use std::any::{Any};
use std::marker::{PhantomData};
use std::fmt;
use tree;
use signal;

use super::core::{
  GUI, GUIContext, ComponentAny, LiveTree,
  dynamic,
};

////////////////////////////////////////////////////////////////////////////////////////////////////
// Component trait

/// Trait for user-definable GUI components.
pub trait Component: Sized + PartialEq + 'static {
  type State: PartialEq;

  /// Generate an initial state for this component.
  fn init(&self) -> Self::State;

  /// Render this component when it has the given state.
  fn render(&self, ctx: Ctx<Self>) -> GUI;

  /// Return `true` if the next configuration/state indicates that the component should be updated
  /// and rerendered.
  fn should_update(&self, s: &Self::State, next: &Self, next_s: &Self::State) -> bool {
    *self != *next || *s != *next_s
  }

  /// Create a GUI element for this component.
  fn into_gui(self) -> GUI { dynamic(self) }
}

// Pure component trait

pub trait PureComponent: Sized + Eq + 'static {
  /// Render this pure component.
  fn render(&self) -> GUI;
  /// Create a GUI element for this pure component.
  fn into_gui(self) -> GUI { dynamic(PureComp(self)) }
}

#[derive(PartialEq)]
struct PureComp<T>(T);

impl<T> Component for PureComp<T> where T: PureComponent {
  type State = ();
  fn init(&self) {}
  fn render(&self, _: Ctx<Self>) -> GUI { self.0.render() }
  fn should_update(&self, _: &(), next: &Self, _: &()) -> bool { self.0 != next.0 }
}

// Auto-promote Component to ComponentAny by erasing & recovering type info.

impl<T> ComponentAny for T where T: Component {
  fn as_any(&self) -> &dyn Any {
    self
  }

  fn init(&self) -> Box<dyn Any> {
    Box::new(T::init(self))
  }

  fn render(&self, ctx: &GUIContext, s_erased: &dyn Any) -> GUI {
    let state = s_erased.downcast_ref::<T::State>()
      .expect("current state to render() should have correct type");
    T::render(self, Ctx::new(ctx, state))
  }

  fn compatible(&self, cfg: &dyn Any) -> bool {
    cfg.is::<T>()
  }

  fn eq(&self, oth_erased: &dyn Any) -> bool {
    oth_erased
      .downcast_ref::<T>()
      .map(|oth| *oth == *self)
      .unwrap_or(false)
  }

  fn should_update(&self,
                   s_erased: &dyn Any,
                   next_erased: &dyn Any,
                   next_s_erased: &dyn Any) -> bool {
    let s = s_erased.downcast_ref::<T::State>()
      .expect("prev state to should_update() should have correct type");
    match (next_erased.downcast_ref::<T>(),
           next_s_erased.downcast_ref::<T::State>()) {
      (Some(next), Some(next_s)) =>
        T::should_update(self, s, next, next_s),

      (_, _) => {
        println!("warning: dynamic typechecking failed in should_update()");
        false
      }
    }
  }
}

////////////////////////////////////////////////////////////////////////////////////////////////////
// Context

/// User-facing context. Contains the component's state, and (will) give hooks for creating event
/// callbacks and modifying the state.
pub struct Ctx<'a, C: Component> {
  //path: &'a tree::Path,
  ctx: &'a GUIContext,
  state: &'a C::State,
}

impl<'a, C: Component> fmt::Debug for Ctx<'a, C>
where C::State: fmt::Debug {
  fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    write!(f, "Ctx {{ state: {:?} }}", self.state)
  }
}

impl<'a, C: Component> Clone for Ctx<'a, C> {
  fn clone(&self) -> Self {
    let Ctx { ctx, state } = *self;
    Ctx { ctx, state }
  }
}

impl<'a, C: Component> Ctx<'a, C> {
  fn new(ctx: &'a GUIContext, state: &'a C::State) -> Self {
    Ctx { ctx, state }
  }

  // State

  pub fn state(&self) -> &C::State {
    self.state
  }


  // Callback creation

  pub fn callback<A, F>(&self, func: F) -> impl signal::Sink<A>
  where F: FnMut(&tree::Path, &A) + 'static,
        A: ?Sized + 'static {
    self.ctx.regis.sink_from_callback(CtxCallback {
      phantom: PhantomData,
      path: self.ctx.path.clone(),
      func,
    })
  }
}

// signal::Callback<> impl for callback functions
struct CtxCallback<F, A: ?Sized> {
  phantom: PhantomData<fn(&A)>,
  path: tree::PathBuf,
  func: F,
}

impl<F, A> signal::Callback<LiveTree, A> for CtxCallback<F, A>
where F: FnMut(&tree::Path, &A) + 'static,
      A: ?Sized + 'static {
  fn call(&mut self, _: &mut LiveTree, arg: &A) {
    (self.func)(&self.path, arg)
  }
}
